﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Glodon.Gdmp.DB;
using Glodon.Gdmp.UI;

namespace Glodon.Gdmp.SDK.Samples.ElementGeometry
{
    /// <summary>
    /// 此示例展示了通过几何计算两堵墙的抹灰面积。
    /// </summary>
    public class ElementGeometryCmd : IExternalCommand
    {
        private Document _document;
        public Result Execute(UIDocument uiDocument)
        {
            try
            {
                // 载入示例项目文档。
                UIApplication app = UIApplication.Get();
                string location = Assembly.GetExecutingAssembly().Location;
                string sampleFilePath = Path.Combine($"{ Path.GetDirectoryName(location)}", "ElementGeometrySample.glab");
                //文档从未打开时运行，或者运行后关闭文档重新运行，需要打开文档
                if (_document == null || !_document.IsValidObject())
                {
                    var newUIDocument = app.OpenAndActiveDocument(sampleFilePath);
                    //保存文档对象，下次运行时，如果文档还未关闭，可以复用
                    _document = newUIDocument.Document;
                }

                Element wall1 = _document.GetElement(new ElementId(18014123631575905));
                Element wall2 = _document.GetElement(new ElementId(18014123631575908));

                double totalArea = 0.0;

                //判断是否有扣减情况。
                var exist1 = InstanceVoidCutUtils.CutExists(_document, wall1.Id, wall2.Id);
                if(exist1)
                {
                    MessageBoard.Show($"不支持扣减的情况", "GDMPLab");
                    return Result.Cancelled;
                }
                var exist2 = InstanceVoidCutUtils.CutExists(_document, wall2.Id, wall1.Id);
                if (exist2)
                {
                    MessageBoard.Show($"不支持扣减的情况", "GDMPLab");
                    return Result.Cancelled;
                }

                // 获取墙1的所有侧面积。
                IList<Face> sideFaces1 = GetSideFaces(GetBody(wall1));
                // 获取墙2的所有侧面积。
                IList<Face> sideFaces2 = GetSideFaces(GetBody(wall2));

                // 获取墙1与墙2的相交部分。
                IList<Face> intersectSideFaces = new List<Face>();
                Body intersect = BooleanOperationUtils.ExecuteBooleanOperation(GetBody(wall1), GetBody(wall2), BooleanOperationType.Intersect);
                if(intersect != null)
                {
                    intersectSideFaces = GetSideFaces(intersect);
                }
                
                // 计算面积。
                foreach (var tf in sideFaces1)
                {
                    totalArea += tf.Area;
                }
                foreach (var tf in sideFaces2)
                {
                    totalArea += tf.Area;
                }
                foreach (var tf in intersectSideFaces)
                {
                    totalArea -= tf.Area;
                }
                MessageBoard.Show($"抹灰面积为：{Math.Round(totalArea, 2).ToString()}", "GDMPLab");
                return Result.Succeeded;
            }
            catch (Exception ex)
            {
                var message = ex.Message;
                return Result.Failed;
            }
        }

        #region 获取图元的体
        /// <summary>
        /// 获取图元的体。
        /// 从Element.Geometry获取几何的结果是图形节点集合。
        /// 每一个图形节点可能还是一个集合对象，如GraphicsNodeGroup，GraphicsElementShape等，
        /// 它们的基类都是GraphicsNode，需要继续遍历，直至找到GraphicsBody。
        /// </summary>
        /// <param name="element">图元。</param>
        /// <returns>图元的体。</returns>
        private Body GetBody(Element element)
        {
            Document document = element.Document;
            // 获取图元图形节点。
            GraphicsElementShape geometry = element.GetGeometry(null);
            if (geometry == null) return null;
            // 获取所有节点。
            IList<GraphicsNode> nodes = geometry.GetNodes();
            List<Body> bodies = new List<Body>();
            foreach (var node in nodes)
            {
                // 若节点就是体的图形节点，则直接添加。
                if (node is GraphicsBody)
                {
                    bodies.Add((node as GraphicsBody).Body);
                }
                // 若是图形节点组，则需要继续遍历获取体的图形节点。
                if (node is GraphicsNodeGroup)
                {
                    GraphicsNodeGroup nodeGroup = node as GraphicsNodeGroup;
                    IList<GraphicsNode> groups = nodeGroup.GetNodes();
                    foreach (var n in groups)
                    {
                        if (n is GraphicsBody)
                        {
                            bodies.Add((n as GraphicsBody).Body);
                        }
                    }
                }
                // 若是图元图形节点，则在节点集合中添加体的图形节点。
                if (node is GraphicsElementShape)
                {
                    GraphicsElementShape nodeShape = node as GraphicsElementShape;
                    IList<GraphicsNode> groups = nodeShape.GetNodes();
                    foreach (var n in groups)
                    {
                        if (n is GraphicsBody)
                        {
                            bodies.Add((n as GraphicsBody).Body);
                        }
                    }
                }
                // 若是图元图形节点引用，则需要获取所引用的源图元。
                if (node is GraphicsElementShapeReference)
                {
                    GraphicsElementShapeReference nodeShapeReference = node as GraphicsElementShapeReference;
                    ElementId referenceId = nodeShapeReference.ElementId;
                    Element referenceElem = document.GetElement(referenceId);
                    GraphicsElementShape referenceGeometry = referenceElem.GetGeometry(null);
                    IList<GraphicsNode> referenceNodes = referenceGeometry.GetNodes();
                    foreach (var n in referenceNodes)
                    {
                        if (n is GraphicsBody)
                        {
                            Body body = (n as GraphicsBody).Body;
                            Body transformedBody = body.CreateTransformed(nodeShapeReference.Transform);
                            bodies.Add(transformedBody);
                        }
                    }
                }
            }
            if (!bodies.Any()) return null;
            return bodies.OrderByDescending(x => x.Volume).FirstOrDefault();
        }
        #endregion

        #region 获取侧面积
        /// <summary>
        /// 通过判断面的法线排除掉顶面和底面
        /// </summary>
        /// <param name="body">图元体</param>
        /// <returns></returns>
        private IList<Face> GetSideFaces(Body body)
        {
            IList<Face> faces = body.Faces;
            IList<Face> sideFaces = new List<Face>();
            // 遍历所有拓扑面。
            foreach (var f in faces)
            {
                // 墙的表面都是平面。
                Plane plane = f.Surface as Plane;
                if (null != plane)
                {
                    // 将顶部与底部的面积排除。
                    if (!(plane.Normal.Normalize().IsAlmostEqualTo(Vector3d.BasisZ) ||
                         plane.Normal.Normalize().IsAlmostEqualTo(Vector3d.BasisZ.Negate())))
                    {
                        sideFaces.Add(f);
                    }
                }
            }
            return sideFaces;
        }
        #endregion
    }
}
